/*
 * Copyright © 2009 Gerald Isaac Schwarz
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package driver;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Color;
import java.util.ArrayList;
import javax.swing.event.EventListenerList;

import base.Engine;

public class HotSeatDriver implements Driver {
	private base.Engine engine;
	private base.Board board;
	private ArrayList<Player> players;
	private Player currentPlayer;
	private boolean hasMoved;
	private boolean hasUpgraded;
	private EventListenerList listenerList = new EventListenerList();
	
	public HotSeatDriver(base.Engine engine) {
		this.engine = engine;
		this.board = engine.getVirtualBoard();
		this.players = new ArrayList<Player>();
		
		ArrayList<base.Player> realPlayers = engine.getPlayers();
		
		for (base.Player player : realPlayers) {
			this.players.add(new Player(player.getName(), player.getType(), player.getColor()));
		}
		
		this.currentPlayer = this.getPlayerByRealPlayer(this.engine.getCurrentPlayer());
		this.hasMoved = false;
	}
	
	@Override
	public ArrayList<String> getTypes() {
		ArrayList<String> types = new ArrayList<String>();
		
		for (base.Piece type : this.engine.getTypes()) {
			types.add(type.getType());
		}
		return types;
	}
	
	@Override
	public Piece getPiece(Point location) {
		for (Piece piece : this.getPieces()) {
			if (piece.getLocation().equals(location)) {
				return piece;
			}
		}
		
		return null;
	}
	
	@Override
	public ArrayList<String> getUpgrades(Point location) {
		ArrayList<String> upgrades = new ArrayList<String>();
		
		if (!this.isEmpty(location)) {
			base.Piece realPiece = this.getRealPiece(location);
			for (String upgrade : realPiece.getUpgradeOptions()) {
				upgrades.add(upgrade);
			}
		}
		
		return upgrades;
	}
	
	@Override
	public boolean isEmpty(Point location) {
		if (this.getPiece(location) == null) {
			return true;
		}
		else {
			return false;
		}
	}
	
	@Override
	public void addTurnListener(TurnListener turnListener) {
		listenerList.add(TurnListener.class, turnListener);
	}
	
	@Override
	public boolean canBeUpgraded(Point location) {
		base.Piece realPiece = this.getRealPiece(location);
		
		return this.supportsUpgrade() && realPiece != null && this.board.isInUpgradeZone(realPiece) && 
				this.board.getCurrentState(realPiece).getOwner() == this.board.getCurrentPlayer() && 
				this.board.getCurrentState(realPiece).isAlive() && 
				this.engine.isUpgradeable(realPiece);
	}

	@Override
	public void castle(Point location) throws CastleNotSupportedException, InvalidCastleException {
		if (this.supportsCastle()) {
			if (!this.hasMoved && !this.isEmpty(location) && this.engine.castle(this.getRealPiece(location))) {
				this.hasMoved = true;
			}
			else {
				throw new InvalidCastleException();
			}
		}
		else {
			throw new CastleNotSupportedException();
		}
	}

	@Override
	public void drop(int index, Point destination) throws DropNotSupportedException, InvalidDropException {
		if (this.supportsDrop()) {
			base.Piece realPiece = this.getRealCapturedPiece(index);
			if (!this.hasMoved && realPiece != null && this.engine.drop(this.getRealCapturedPiece(index), destination)) {
				this.hasMoved = true;
			}
			else {
				throw new InvalidDropException();
			}
		}
		else {
			throw new DropNotSupportedException();
		}
	}
	
	@Override
	public void endTurn() throws TurnNotFinishedException {
		if (this.hasMoved && !thereIsStillUnfinishedBusinessToTakeCareOf()) {
			this.hasMoved = false;
			this.hasUpgraded = false;
			this.currentPlayer = this.getPlayerByRealPlayer(this.engine.nextPlayer());
			this.notifyTurnListeners(new TurnEvent(this));
		}
		else {
			throw new TurnNotFinishedException();
		}
	}

	@Override
	public Dimension getBoardSize() {
		return this.engine.getBoard().getSize();
	}

	@Override
	public ArrayList<Piece> getCapturedPieces(int index) {
			ArrayList<Piece> capturedPieces = new ArrayList<Piece>();
			Player player = this.players.get(index);
			base.Player realPlayer = this.engine.getPlayers().get(index);
			
			for (base.Piece realPiece : this.engine.getBoard().getCapturedPieces(realPlayer)) {
				capturedPieces.add(new Piece(realPiece.getLocation(), player, realPiece.getDirection(), realPiece.getType(), realPiece.getRepresentation(), realPiece.getUpgrade().getType()));
			}
		
		return capturedPieces;
	}

	@Override
	public int getCurrentPlayerIndex() {
		return this.players.indexOf(this.currentPlayer);
	}
	
	@Override
	public Player getCurrentPlayer() {
		return this.currentPlayer;
	}
	
	@Override
	public Player getPlayerByIndex(int index) {
		if (index >= 0 && index < this.players.size()) {
			return this.players.get(index);
		}
		else {
			return null;
		}
	}
	
	private Player getPlayerByRealPlayer(base.Player realPlayer) {
		return this.players.get(this.engine.getPlayers().indexOf(realPlayer));
	}
	
	private base.Player getRealPlayerByPlayer(Player player) {
		return this.engine.getPlayers().get(this.getPlayerIndex(player));
	}
	
	@Override
	public int getPlayerIndex(Player player) {
		return this.players.indexOf(player);
	}
	
	@Override
	public int getMaximumNumberOfPlayers() {
		return this.engine.getMaximumNumberOfPlayers();
	}

	@Override
	public int getMinimumNumberOfPlayers() {
		return this.engine.getMinimumNumberOfPlayers();
	}

	@Override
	public ArrayList<Piece> getPieces() {
		ArrayList<Piece> pieces = new ArrayList<Piece>();
		
		for (base.Piece realPiece : this.engine.getBoard().getPieces()) {
			if (realPiece.isAlive()) {
				Player player = this.getPlayerByRealPlayer(realPiece.getOwner());
				Piece piece = new Piece(realPiece.getLocation(), player, realPiece.getDirection(), realPiece.getType(), realPiece.getRepresentation(), realPiece.getUpgrade().getType());
				pieces.add(piece);
			}
		}
		
		return pieces;
	}
	
	private ArrayList<Piece> getPiecesOnBoardByPlayer(Player player) {
		ArrayList<Piece> pieces = new ArrayList<Piece>();
		base.Player realPlayer = this.getRealPlayerByPlayer(player);
		
		for (base.Piece realPiece : this.engine.getBoard().getPieces()) {
			if (realPiece.isAlive() && realPiece.getOwner() == realPlayer) {
				Piece piece = new Piece(realPiece.getLocation(), player, realPiece.getDirection(), realPiece.getType(), realPiece.getRepresentation(), realPiece.getUpgrade().getType());
				pieces.add(piece);
			}
		}
		
		return pieces;
	}

	@Override
	public ArrayList<Player> getPlayers() {
		return new ArrayList<Player>(this.players);
	}

	@Override
	public boolean isInCheck(int index) {
		if (this.getPlayerByIndex(index) != null) {
			return this.engine.isCheck(this.engine.getPlayers().get(index));
		}
		else {
//			TODO Throw an exception instead.
			return false;
		}
	}

	@Override
	public boolean isInCheckmate(int index) {
		if (this.getPlayerByIndex(index) != null) {
			this.engine.getVirtualEngine().setTurns(new ArrayList<base.Turn>());
			return this.engine.getVirtualEngine().isCheckmate(this.engine.getPlayers().get(index));
		}
		else {
//			TODO Throw an exception instead.
			return false;
		}
	}

	@Override
	public boolean isValidCastle(Point location) throws CastleNotSupportedException {
		if (this.supportsCastle()) {
			if (!this.isEmpty(location)) {
				return this.engine.isValidCastle(this.getRealPiece(location));
			}
			else {
//				TODO Throw an exception instead.
				return false;
			}
		}
		else {
			throw new CastleNotSupportedException();
		}
	}

	@Override
	public boolean isValidDrop(int index, Point destination) throws DropNotSupportedException {
		if (this.supportsDrop()) {
			if (this.getRealCapturedPiece(index) != null) {
				return this.engine.isValidDrop(this.getRealCapturedPiece(index), destination);
			}
			else {
//				TODO Throw an exception instead.
				return false;
			}
		}
		else {
			throw new DropNotSupportedException();
		}
	}

	@Override
	public boolean isValidMove(Point location, Point destination) throws MoveNotSupportedException {
		if (this.supportsMove()) {
			if (!this.isEmpty(location)) {
				return this.engine.isValidMove(this.getRealPiece(location), destination);
			}
			else {
//				TODO Throw an exception instead.
				return false;
			}
		}
		else {
			throw new MoveNotSupportedException();
		}
	}

	@Override
	public boolean isValidUpgrade(Point location, String type) throws UpgradeNotSupportedException {
		if (this.supportsUpgrade()) {
			if (!this.isEmpty(location)) {
				return this.engine.isValidUpgrade(this.getRealPiece(location), type);
			}
			else {
//				TODO Throw an exception instead.
				return false;
			}
		}
		else {
			throw new UpgradeNotSupportedException();
		}
	}
	
	@Override
	public void move(Point location, Point destination) throws MoveNotSupportedException, InvalidMoveException {
		if (this.supportsMove()) {
			if (!this.hasMoved && !this.isEmpty(location) && this.engine.move(this.getRealPiece(location), destination)) {
				this.hasMoved = true;
			}
			else {
				throw new InvalidMoveException();
			}
		}
		else {
			throw new MoveNotSupportedException();
		}
	}
	
	@Override
	public boolean mustBeUpgraded(Point location) {
		if (!this.isEmpty(location)) {
			base.Piece realPiece = this.getRealPiece(location);
			return this.engine.isStuck(realPiece);
		}
		else {
//			TODO Throw an exception instead.
			return false;
		}
	}
	
	@Override
	public void removeTurnListener(TurnListener turnListener) {
		listenerList.remove(TurnListener.class, turnListener);
	}
	
	@Override
	public void setPlayer(Player player) throws TooFewPlayersException, TooManyPlayersException {
		throw new TooFewPlayersException();
	}
	
	@Override
	public void setPlayers(ArrayList<Player> players) throws TooFewPlayersException, TooManyPlayersException {
		if (players.size() < this.getMinimumNumberOfPlayers()) {
			throw new TooFewPlayersException();
		}
		else if (players.size() > this.getMaximumNumberOfPlayers()) {
			throw new TooManyPlayersException();
		}
		else {
			this.players = new ArrayList<Player>(players);
			
			ArrayList<base.Player> realPlayers = new ArrayList<base.Player>();
			
			for (Player player : this.players) {
				int direction = player.getColor().equals(Color.BLACK) ? base.Piece.SOUTH : base.Piece.NORTH;
				realPlayers.add(new base.Player(player.getName(), player.getColor(), direction));
			}
			
			this.engine.setPlayers(realPlayers);
			this.currentPlayer = this.getPlayerByRealPlayer(this.engine.getCurrentPlayer());
			this.hasMoved = false;
		}
	}
	
	@Override
	public boolean supportsCastle() {
		return this.engine.supportsCastle();
	}
	
	@Override
	public boolean supportsDrop() {
		return this.engine.supportsDrop();
	}
	
	@Override
	public boolean supportsMove() {
		return this.engine.supportsMove();
	}
	
	@Override
	public boolean supportsUpgrade() {
		return this.engine.supportsUpgrade();
	}
	
	@Override
	public void upgrade(Point location, String type) throws UpgradeNotSupportedException, InvalidUpgradeException {
		if (this.supportsUpgrade()) {
			if (!this.hasUpgraded && !this.isEmpty(location) && this.engine.upgrade(this.getRealPiece(location), type)) {
				this.hasUpgraded = true;
			}
			else {
				throw new InvalidUpgradeException();
			}
		}
		else {
			throw new UpgradeNotSupportedException();
		}
	}
	
	private base.Piece getRealPiece(Point location) {
		return this.engine.getBoard().getPiece(location);
	}
	
	private base.Piece getRealCapturedPiece(int index) {
		ArrayList<base.Piece> realPieces = this.engine.getBoard().getCapturedPieces(this.engine.getCurrentPlayer());
		
		if (realPieces.size() > index && index >= 0) {
			return realPieces.get(index);
		}
		else {
			return null;
		}
		
	}
	
	private boolean thereIsStillUnfinishedBusinessToTakeCareOf() {
		for (Piece piece : this.getPiecesOnBoardByPlayer(this.currentPlayer)) {
			if (this.mustBeUpgraded(piece.getLocation())) {
				return true;
			}
		}
		
		return false;
	}
	
	private void notifyTurnListeners(TurnEvent turnEvent) {
		Object[] listeners = listenerList.getListenerList();
		
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == TurnListener.class) {
            	((TurnListener)listeners[i + 1]).turnPerformed(turnEvent);
            }
        }
	}

	@Override
	public Engine getEngine() {
		return this.engine;
	}
}
